<h3>{__('describe / histogram')}</h3>
<svg ref:svg>
    <!-- x axis -->
    <g transform="translate({[padding.left,padding.top]})">
        <g class="axis x-axis" transform="translate(0, {innerHeight})">
            {#each ticks as tick}
            <g class="tick" transform="translate({xScale(tick.x)},0)">
                <line y2="3" />
                <text y="5">{tick.label}</text>
            </g>
            {/each} {#if highlight}
            <polygon transform="translate({xScale(highlight.x)},0)" points="0,0,4,6,-4,6" />
            {/if}
        </g>

        <!--         <g class='bars'>
            {{#each bins as bin, i}}
            <g class="bar" transform="translate({{xScale(bin.x0)}},{{yScale(bin.length)}})">
                <title>{{tooltip(bin,i,bins,validValues.length)}}</title>
                <rect x="0"
                    width="{{ bin.x1 != bin.x0 ? xScale(bin.x1)-xScale(bin.x0)-1 : 20 }}"
                    height='{{ innerHeight - yScale(bin.length) }}'
                ></rect>
            </g>
            {{/each}}
        </g> -->

        <g class="bars">
            {#each bins as bin, i}
            <g class="bar" transform="translate({xScaleBand(bin.x0)},{yScale(bin.length)})">
                <title>{tooltip(bin,i,bins,validValues.length)}</title>
                <rect
                    width="{ bin.x1 != bin.x0 ? xScaleBand.bandwidth() : 20 }"
                    height="{ innerHeight - yScale(bin.length) }"
                ></rect>
            </g>
            {/each}
        </g>
    </g>
</svg>
<ul>
    {#each stats as s}
    <li>{s.name}: <tt on:mouseleave="show(false)" on:mouseenter="show(s)">{s.label}</tt></li>
    {/each} {#if NAs>0}
    <li>
        {__('describe / histogram / invalid')}:
        <tt style="color: #c71e1d">{NAs}</tt> ({pct(NAs/values.length)})
    </li>
    {/if}
</ul>
<p class="learn-more">{@html __("describe / histogram / learn-more")}</p>

<script>
    import range from 'lodash/range';
    import countBy from 'lodash/countBy';
    import toFixed from '@datawrapper/shared/toFixed';
    import { __ } from '@datawrapper/shared/l10n';
    import { scaleLinear, scaleBand } from 'd3-scale';
    import { histogram, max, min, extent, mean, median, thresholdSturges } from 'd3-array';

    var xScale_ = scaleLinear();
    var xScaleBand_ = scaleBand();
    var yScale_ = scaleLinear();

    const pct = val => {
        if (!val) return '0%';
        if (val < 0.01) return '<1%';
        return (val * 100).toFixed(0) + '%';
    };

    export default {
        data() {
            return {
                format: d => d,
                t: 0,
                padding: { top: 10, right: 65, bottom: 20, left: 5 },
                height: 200,
                width: 500,
                values: [],
                highlight: false
            };
        },

        computed: {
            NAs({ values }) {
                return values.filter(d => typeof d === 'string' || Number.isNaN(d)).length;
            },

            stats({ validValues, format }) {
                const xmin = min(validValues);
                const xmax = max(validValues);
                const xmean = mean(validValues);
                const xmed = median(validValues);
                return [
                    { x: xmin, label: format(xmin), name: __('describe / histogram / min') },
                    { x: xmax, label: format(xmax), name: __('describe / histogram / max') },
                    { x: xmean, label: format(xmean), name: __('describe / histogram / mean') },
                    { x: xmed, label: format(xmed), name: __('describe / histogram / median') }
                ];
            },

            validValues({ values }) {
                return values.filter(d => typeof d === 'number' && !Number.isNaN(d));
            },

            ticks({ xScale, format }) {
                return xScale.ticks(4).map(x => {
                    return { x, label: format(x) };
                });
            },

            bins({ niceDomain, validValues }) {
                // const tickCnt = Math.min(_uniq(validValues).length, 14);
                const dom = niceDomain;
                // const classw = (s[1]-s[0]);
                const bins = histogram().domain(dom).thresholds(thresholdSturges)(validValues);
                const binWidths = countBy(bins.map(b => b.x1 - b.x0));
                if (bins.length > 2 && Object.keys(binWidths).length > 1) {
                    // check first and last bin
                    const binw = bins[1].x1 - bins[1].x0;
                    const lst = dom[0] + Math.ceil((dom[1] - dom[0]) / binw) * binw;
                    return histogram()
                        .domain([dom[0], lst])
                        .thresholds(range(dom[0], lst + binw * 0.4, binw))(validValues);
                }
                return bins;
            },

            niceDomain({ validValues }) {
                return scaleLinear().domain(extent(validValues)).nice().domain();
            },

            xScaleBand({ bins, innerWidth }) {
                return xScaleBand_
                    .domain(bins.map(d => d.x0))
                    .paddingInner(0.1)
                    .rangeRound([0, innerWidth])
                    .align(0);
            },

            xScale({ niceDomain, bins, xScaleBand }) {
                return xScale_.domain(niceDomain).rangeRound([0, xScaleBand.step() * bins.length]);
            },

            yScale({ innerHeight, bins }) {
                return yScale_
                    .domain([
                        0,
                        max(bins, function (d) {
                            return d.length;
                        })
                    ])
                    .range([innerHeight, 0]);
            },

            barWidth({ bins, xScale }) {
                return xScale(bins[0].x1) - xScale(bins[0].x0) - 1;
            },

            innerWidth({ width, padding }) {
                return width - padding.left - padding.right;
            },
            innerHeight({ height, padding }) {
                return height - padding.bottom - padding.top;
            }
        },

        helpers: {
            tooltip(bin, i, bins, len) {
                const tt =
                    i === 0
                        ? __('describe / histogram / tooltip / first')
                        : i === bins.length - 1
                        ? __('describe / histogram / tooltip / last')
                        : __('describe / histogram / tooltip');
                return tt
                    .replace('$1', bin.length)
                    .replace('$2', pct(bin.length / len))
                    .replace('$3', toFixed(bin.x0))
                    .replace('$4', toFixed(bin.x1));
            },
            pct,
            __
        },

        methods: {
            show(value) {
                this.set({ highlight: value });
            },
            resize: function () {
                var bcr = this.refs.svg.getBoundingClientRect();

                this.set({
                    width: bcr.right - bcr.left,
                    height: bcr.bottom - bcr.top
                });
            }
        },

        oncreate() {
            this.resize();
        }
    };
</script>

<style lang="less">
    h3 {
        margin-top: 25px;
    }
    .chart {
        width: 100%;
        max-width: 500px;
        margin: 0 auto;
    }
    svg {
        overflow: visible;
        position: relative;
        width: 100%;
        height: 135px;
    }
    .tick {
        font-family: Roboto, sans-serif;
        font-size: 0.725em;
        font-weight: 200;
        line {
            shape-rendering: crispEdges;
        }
    }
    .axis line {
        stroke: #888;
        shape-rendering: crispEdges;
    }
    .tick text {
        fill: #888;
        text-anchor: start;
    }
    .x-axis .tick text {
        text-anchor: middle;
        dominant-baseline: hanging;
    }
    .bars rect {
        fill: #cecece;
        stroke: none;
        shape-rendering: crispEdges;
    }
    tt {
        font-size: 13px;
        font-weight: 400;
        font-family: Roboto;
        color: #297ea8;
    }
    ul {
        margin: 0;
        padding: 0;
    }
    ul li {
        display: inline-block;
        margin-right: 1em;
        color: #777;
        font-size: 13px;
    }
    :global(.learn-more) {
        margin-top: 15px;
        font-size: 12px;
        color: #888;
    }
</style>
